package cn.virens.web.components.open.wxmp;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;

import org.apache.http.HttpHost;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import cn.virens.common.fun.ExSupplier;
import me.chanjar.weixin.common.WxType;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.HttpType;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.enums.WxMpApiUrl;
import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder;

@Component
public class WxMpServiceImpl extends BaseWxMpServiceImpl<CloseableHttpClient, HttpHost> implements InitializingBean {
	private final Map<String, WxMpConfigStorage> configStorageMap = new ConcurrentHashMap<>();

	private CloseableHttpClient httpClient;

	public void addConfigStorage(WxMpConfigStorage configStorage) {
		this.configStorageMap.put(configStorage.getAppId(), configStorage);
	}

	@Override
	public void removeConfigStorage(String mpId) {
		this.configStorageMap.remove(mpId);
	}

	@Override
	public WxMpConfigStorage getWxMpConfigStorage() {
		if (configStorageMap.size() == 1) {
			return configStorageMap.values().iterator().next();
		} else {
			return configStorageMap.get(WxMpConfigStorageHolder.get());
		}
	}

	@Override
	@Deprecated
	public void addConfigStorage(String mpId, WxMpConfigStorage configStorages) {
	}

	@Override
	@Deprecated
	public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) {
	}

	@Override
	@Deprecated
	public void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages) {
	}

	@Override
	@Deprecated
	public void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages, String defaultMpId) {
	}

	public String getAppid() {
		return WxMpConfigStorageHolder.get();
	}

	@Override
	public HttpType getRequestType() {
		return HttpType.APACHE_HTTP;
	}

	@Override
	public CloseableHttpClient getRequestHttpClient() {
		return httpClient;
	}

	@Override
	public HttpHost getRequestHttpProxy() {
		return null;
	}

	@Override
	@Deprecated
	public void initHttp() {

	}

	@Override
	public String getAccessToken(boolean forceRefresh) throws WxErrorException {
		WxMpConfigStorage config = this.getWxMpConfigStorage();
		if (!config.isAccessTokenExpired() && !forceRefresh) { //
			return config.getAccessToken();
		}

		// 对当前微信配置信息上锁
		Lock lock = config.getAccessTokenLock();
		lock.lock();

		HttpGet httpGet = null;
		BasicResponseHandler handler = null;
		CloseableHttpResponse response = null;

		try {
			// 构造http请求对象
			httpGet = new HttpGet(GET_ACCESS_TOKEN_URL(config, config.getAppId(), config.getSecret()));
			response = getRequestHttpClient().execute(httpGet);
			handler = new BasicResponseHandler();

			// 执行网络请求
			String resultContent = handler.handleResponse(response);

			// 判断请求是否失败
			WxError error = WxError.fromJson(resultContent, WxType.MP);
			if (error.getErrorCode() != 0) {//
				throw new WxErrorException(error);
			}

			// 更新微信access_token
			WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
			config.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());

			return config.getAccessToken();
		} catch (HttpResponseException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			if (httpGet != null) {
				httpGet.releaseConnection();
			}

			lock.unlock();
		}
	}

	@Override
	public boolean switchover(String appid) {
		if (configStorageMap.containsKey(appid)) {
			WxMpConfigStorageHolder.set(appid);
		} else {
			return false;
		}

		return true;
	}

	@Override
	public WxMpService switchoverTo(String appid) {
		if (configStorageMap.containsKey(appid)) {
			WxMpConfigStorageHolder.set(appid);

			return this;
		}

		throw new RuntimeException(String.format("无法找到对应【%s】的公众号配置信息，请核实！", appid));
	}

	/**
	 * 切换微信应用，并通过回调获取返回值
	 * 
	 * @param  <T>
	 * @param  appid
	 * @param  supplier
	 * @return
	 * @throws Exception
	 */
	public <T> T switchover(String appid, ExSupplier<T> supplier) throws Exception {
		if (configStorageMap.containsKey(appid)) {
			WxMpConfigStorageHolder.set(appid);

			return supplier.get();
		}

		throw new RuntimeException(String.format("无法找到对应【%s】的公众号配置信息，请核实！", appid));
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		this.httpClient = DefaultApacheHttpClientBuilder.get().build();
	}

	private static String GET_ACCESS_TOKEN_URL(WxMpConfigStorage config, String appid, String secret) {
		return String.format(WxMpApiUrl.Other.GET_ACCESS_TOKEN_URL.getUrl(config), appid, secret);
	}
}
